package com.derbysoft.lowcostadapter.schedule.parse.impl;

import com.derbysoft.lowcostadapter.commons.model.Hotel;
import com.derbysoft.lowcostadapter.commons.model.RateCache;
import com.derbysoft.lowcostadapter.commons.model.RoomType;
import com.derbysoft.lowcostadapter.commons.model.Status;
import com.derbysoft.lowcostadapter.commons.redis.service.JedisClusterService;
import com.derbysoft.lowcostadapter.commons.redis.service.RateCacheService;
import com.derbysoft.lowcostadapter.commons.redis.service.RoomTypeCacheService;
import com.derbysoft.lowcostadapter.commons.redis.utils.KeyValueHelper;
import com.derbysoft.lowcostadapter.commons.repository.HotelRepository;
import com.derbysoft.lowcostadapter.commons.utils.DateTimeUtils;
import com.derbysoft.lowcostadapter.dswitch2.translator.impl.MealBasis;
import com.derbysoft.lowcostadapter.schedule.parse.ParseRateCacheService;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Created by: jason
 * Date: 2012-09-05
 */
@Component("parseRateCacheService")
public class ParseRateCacheServiceImpl implements ParseRateCacheService {

    private Logger logger = LoggerFactory.getLogger(ParseRateCacheServiceImpl.class);

    private static final int PRINT_STEP = 100000;
    private static final int batchImportSize = 100000;
    private final AtomicBoolean updateCache = new AtomicBoolean(false);
    private static final String ASTERISK = "*";

    @Autowired
    private HotelRepository hotelRepository;

    @Autowired
    private RateCacheService rateCacheRepository;

    @Autowired
    private RoomTypeCacheService roomTypeCacheService;

    @Autowired
    private JedisClusterService jedisClusterService;

    @Override
    public synchronized boolean importRoomTypes(InputStream inputStream) {
        try {
            updateCache.set(true);
            logger.info("ImportRoomType begin");
            executeImportRoomTypes(inputStream);
            return true;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            logger.info("ImportRoomType end");
            updateCache.set(false);
        }
        return false;
    }

    @Override
    public synchronized boolean importRateCaches(InputStream inputStream) {
        try {
            updateCache.set(true);
            logger.info("ImportRateCache begin");
            executeImportRateCaches(inputStream);
            logger.info("ImportRateCache end");
            return true;

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return false;
        } finally {
            updateCache.set(false);
        }
    }

    private void executeImportRateCaches(InputStream inputStream) throws IOException {
        Map<Integer, Hotel> hotelMap = hotelRepository.loadHotelMap(Status.ENABLED);
        Map<Integer, RoomType> roomTypeMap = loadRoomTypeMap();
        List<RateCache> rateCaches = new ArrayList<RateCache>();
        long totalBytes = inputStream.available();
        LineIterator lineIterator = IOUtils.lineIterator(inputStream, "UTF-8");
        long line = 0;

        while (lineIterator.hasNext()) {
            String rateInfo = lineIterator.nextLine();
            line++;

            String[] split = rateInfo.split("\\|");

            RateCache rateCache = convertRateCache(split, hotelMap, roomTypeMap);
            if (rateCache != null) {
                rateCaches.add(rateCache);
                if (rateCaches.size() >= batchImportSize) {
                    rateCacheRepository.saveAll(flatten(rateCaches));
                    rateCaches.clear();
                }
            }

            if (line % PRINT_STEP == 0) {
                printProcess("ImportRoomRate", totalBytes, inputStream, line);
            }
        }
        rateCacheRepository.saveAll(flatten(rateCaches));
    }

    private List<RateCache> flatten(List<RateCache> rateCaches) {
        List<RateCache> flattenedRateCaches = new ArrayList<RateCache>();
        for (RateCache rateCache : rateCaches) {
            Date begin = DateTimeUtils.parse(rateCache.getStartDate(), DateTimeUtils.DATE_FORMAT);
            Date end = DateTimeUtils.parse(rateCache.getEndDate(), DateTimeUtils.DATE_FORMAT);
            for (int i = 0; i <= DateTimeUtils.getIntervalDays(begin,
                    end).intValue(); i++) {
                Date date = DateTimeUtils.addHours(begin, 24 * i);
                RateCache newRateCache = new RateCache();
                newRateCache.setPropertyID(rateCache.getPropertyID());
                newRateCache.setStartDate(DateTimeUtils.formatDate(date));
                newRateCache.setEndDate(DateTimeUtils.formatDate(date));
                newRateCache.setAdults(rateCache.getAdults());
                newRateCache.setChildren(rateCache.getChildren());
                newRateCache.setDuration(rateCache.getDuration());
                newRateCache.setMealBasis(rateCache.getMealBasis());
                newRateCache.setPropertyRoomTypeID(rateCache.getPropertyRoomTypeID());
                newRateCache.setRate(rateCache.getRate());
                flattenedRateCaches.add(newRateCache);
            }
        }
        return flattenedRateCaches;
    }

    private void executeImportRoomTypes(InputStream inputStream) throws IOException {
        Map<Integer, Hotel> hotelMap = hotelRepository.loadHotelMap(Status.ENABLED);
        Map<Integer, RoomType> roomTypeMap = loadRoomTypeMap();
        List<RoomType> roomTypes = new ArrayList<RoomType>();

        long totalBytes = inputStream.available();
        LineIterator lineIterator = IOUtils.lineIterator(inputStream, "UTF-8");
        long line = 0;
        while (lineIterator.hasNext()) {
            String rateInfo = lineIterator.nextLine();
            line++;

            String[] infos = rateInfo.split("\\|");

            RoomType roomType = findRoomType(infos, roomTypeMap, hotelMap);
            int propertyRoomTypeID = getIntVal(infos, Field.PROPERTY_ROOM_TYPE_ID);
            if (roomType == null && !roomTypeMap.containsKey(propertyRoomTypeID)) {
                roomType = convertRoomType(infos, roomTypeMap);
                if (roomType != null) {
                    roomTypes.add(roomType);
                }
            }
            if (roomTypes.size() >= batchImportSize) {
                roomTypeCacheService.saveAll(roomTypes);
                roomTypes.clear();
            }

            if (line % PRINT_STEP == 0) {
                printProcess("ImportRoomType", totalBytes, inputStream, line);
            }
        }
        roomTypeCacheService.saveAll(roomTypes);
    }

    private void printProcess(String title, long totalBytes, InputStream inputStream, long line) throws IOException {
        long available = inputStream.available();
        long processed = totalBytes - available;
        double percent = 100.0 * processed / totalBytes;
        String message = String.format("%s Line:(%d) Processed:(%d/%d) Precent:(%.2f%%)",
                title, line, processed, totalBytes, percent);
        logger.info(message);
    }

    private Map<Integer, RoomType> loadRoomTypeMap() {
        Map<Integer, RoomType> roomTypeMap = new HashMap<Integer, RoomType>();
        Collection<String> keys = jedisClusterService.keys(RoomType.PREFIX + KeyValueHelper.SEPARATOR + ASTERISK);
        for (String key : keys) {
            RoomType roomType = roomTypeCacheService.load(key);
            if (roomType != null) {
                roomTypeMap.put(roomType.getPropertyRoomTypeID(), roomType);
            }
        }
        return roomTypeMap;
    }

    public RateCache convertRateCache(String[] infos,
                                      Map<Integer, Hotel> hotelMap,
                                      Map<Integer, RoomType> roomTypeMap) {
        try {
            Hotel hotel = hotelMap.get(getIntVal(infos, Field.PROPERTY_ID));
            if (hotel == null) {
                return null;
            }
            RoomType roomType = roomTypeMap.get(getIntVal(infos, Field.PROPERTY_ROOM_TYPE_ID));
            if (roomType == null) {
                return null;
            }
            RateCache rateCache = new RateCache();
            rateCache.setPropertyID(String.valueOf(hotel.getPropertyID()));
            rateCache.setStartDate(getDateToStringVal(infos, Field.START_DATE));
            rateCache.setEndDate(getDateToStringVal(infos, Field.END_DATE));
            int duration = getIntVal(infos, Field.DURATION);
            rateCache.setDuration(duration);
            rateCache.setAdults(getIntVal(infos, Field.ADULTS));
            rateCache.setChildren(getIntVal(infos, Field.CHILDREN));
            rateCache.setMealBasis(MealBasis.fromID(getIntVal(infos, Field.MEAL_BASIS_ID)));
            rateCache.setPropertyRoomTypeID(roomType.getPropertyRoomTypeID());
            BigDecimal decimalVal = getDecimalVal(infos, Field.PRICE);
            rateCache.setRate(decimalVal.divide(BigDecimal.valueOf(duration == 0 ? 1 : duration), RateCache.SCALE, RoundingMode.HALF_UP));
            return rateCache;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    private RoomType convertRoomType(String[] infos, Map<Integer, RoomType> roomTypeMap) {
        int propertyRoomTypeID = getIntVal(infos, Field.PROPERTY_ROOM_TYPE_ID);
        RoomType roomType = new RoomType();
        roomType.setPropertyRoomTypeID(propertyRoomTypeID);
        String roomTypeCode = getStringVal(infos, Field.ROOM_TYPE_CODE);
        if (StringUtils.isBlank(roomTypeCode)) {
            return null;
        }
        roomType.setRoomTypeCode(roomTypeCode);
        String roomTypeDescription = getStringVal(infos, Field.ROOM_TYPE_DESCRIPTION);
        if (StringUtils.isBlank(roomTypeDescription)) {
            return null;
        }
        roomType.setDescription(roomTypeDescription);
        roomTypeMap.put(roomType.getPropertyRoomTypeID(), roomType);
        return roomType;
    }

    private RoomType findRoomType(String[] infos,
                                  Map<Integer, RoomType> roomTypeMap,
                                  Map<Integer, Hotel> hotelMap) {
        int propertyID = getIntVal(infos, Field.PROPERTY_ID);
        if (!hotelMap.containsKey(propertyID)) {
            return null;
        }

        int propertyRoomTypeID = getIntVal(infos, Field.PROPERTY_ROOM_TYPE_ID);
        if (roomTypeMap.containsKey(propertyRoomTypeID)) {
            return roomTypeMap.get(propertyRoomTypeID);
        }
        return null;
    }

    private BigDecimal getDecimalVal(String[] infos, Field price) {
        return new BigDecimal(getStringVal(infos, price));
    }

    private String getStringVal(String[] infos, Field field) {
        return infos[field.ordinal()];
    }

    private int getIntVal(String[] infos, Field field) {
        return Integer.valueOf(getStringVal(infos, field));
    }

    private Date getDateVal(String[] infos, Field field) {
        return DateTimeUtils.parse(getStringVal(infos, field), "dd-MM-yyyy");
    }

    private String getDateToStringVal(String[] infos, Field field) {
        return DateTimeUtils.formatDate(getDateVal(infos, field));
    }

    public void setHotelRepository(HotelRepository hotelRepository) {
        this.hotelRepository = hotelRepository;
    }

    private enum Field {
        PROPERTY_ID,
        PROPERTY_REF,
        START_DATE,
        END_DATE,
        DURATION,
        AIRPORT_CODE,
        RESORT_ID,
        RESORT_NAME,
        HOTEL_NAME,
        RATING,
        ADULTS,
        CHILDREN,
        INFANTS,
        MEAL_BASIS_CODE,
        MEAL_BASIS_DESCRIPTION,
        MEAL_BASIS_ID,
        PROPERTY_ROOM_TYPE_ID,
        ROOM_TYPE_CODE,
        ROOM_TYPE_DESCRIPTION,
        PRICE
    }

    @Override
    public boolean isParsing() {
        return updateCache.get();
    }
}
